#pragma once

#include <glm\glm.hpp>
#include <vector>
#include <cassert>

#include "Buffer.h"
#include "MeshMgr.h"


#define ADDITIONAL_UV_COUNT 4
#define MAX_UV_COUNT (ADDITIONAL_UV_COUNT+1)

struct VertexData {
	glm::vec3 position;
	glm::vec3 normal;
	glm::vec2 uvCoordinates;
	glm::vec2 additionalUVCoordinates[ADDITIONAL_UV_COUNT];
	unsigned short additionalUVUsed;

	VertexData(): additionalUVUsed(0) {}

	VertexData(glm::vec4 position, glm::vec4 normal, glm::vec3 uvCoordinates): 
		position(position), normal(normal), uvCoordinates(uvCoordinates), additionalUVUsed(0) 
	{	}

	VertexData(glm::vec4 position, glm::vec4 normal, std::vector<glm::vec3> uvCoordinates): 
		position(position), normal(normal)
	{
		assert(uvCoordinates.size() > 0);
		assert(uvCoordinates.size() < MAX_UV_COUNT);
		this->additionalUVUsed = 0;

		this->uvCoordinates = uvCoordinates[0];
		for(unsigned int i = 0; i<uvCoordinates.size(); ++i) {
			this->additionalUVCoordinates[i] = uvCoordinates[i+1];
			++this->additionalUVUsed;
		}

	}
};

class VertexBuffer: public Buffer
{
private:

	struct DeviceVertexData {
		float px,py,pz;
		float nx,ny,nz;
		float u,v;
	};

	struct DeviceUVData {
		float u,v;
	};

	struct DeviceAdditionalUVData {
		DeviceUVData d[ADDITIONAL_UV_COUNT];
	};

#ifdef _OPENGL
	struct BufferInfo {
		GLuint vertexVBOID;
		GLuint additionalVBOID;
	};
#endif

#ifdef _DIRECTX
	struct BufferInfo {

	};
#endif

	VertexData* m_VertexData;
	unsigned int m_VertexCount;

	RHANDLE m_DeviceVertexDataHandle;
	DeviceVertexData* m_DeviceVertexData;

	RHANDLE m_DeviceAdditionalDataHandle;
	DeviceAdditionalUVData* m_DeviceAdditionalData;
	
	BufferInfo m_BufferInfo;

	bool hasAdditionalData() {
		return m_DeviceAdditionalData != NULL;
	}

	// API dependant
	int updateBuffer();
	void releaseBuffer();

	int updateDeviceData() {

		if(m_DeviceVertexData != NULL)
			delete[] m_DeviceVertexData;

		if(m_DeviceAdditionalData != NULL)
			delete[] m_DeviceAdditionalData;

		m_DeviceVertexData = new DeviceVertexData[m_VertexCount];
		m_DeviceAdditionalData = new DeviceAdditionalUVData[m_VertexCount];
		// acquire memory from memory manager
		//m_DeviceVertexData = MemoryMgr::getInstance().acquireArray(	MeshMgr::getInstance().getVertexBufferStack(), 
		//														m_VertexCount,
		//														m_DeviceVertexDataHandle );
		//
		//m_DeviceAdditionalData = MemoryMgr::getInstance().acquireArray(	MeshMgr::getInstance().getVertexBufferStack(), 
		//														m_VertexCount,
		//														m_DeviceAdditionalDataHandle );

		bool additionalDataUsed = false;

		for(unsigned int i=0; i<m_VertexCount; ++i) {
			DeviceVertexData* dv = m_DeviceVertexData+i;
			DeviceAdditionalUVData* avd = m_DeviceAdditionalData+i;
			VertexData* v = m_VertexData+i;

			dv->px = v->position.x;
			dv->py = v->position.y;
			dv->pz = v->position.z;
			
			dv->nx = v->normal.x;
			dv->ny = v->normal.y;
			dv->nz = v->normal.z;

			dv->u = v->uvCoordinates.s;
			dv->v = v->uvCoordinates.t;

			additionalDataUsed |= v->additionalUVUsed > 0;

			for(unsigned a=0; a<v->additionalUVUsed; ++a) {
				avd->d[a].u = v->additionalUVCoordinates[a].s;
				avd->d[a].v = v->additionalUVCoordinates[a].t;
			}
		}

		if(!additionalDataUsed) {
			delete[] m_DeviceAdditionalData;
			m_DeviceAdditionalData = NULL;
		}

		return updateBuffer();
		
	}

public:
/*
	VertexBuffer(std::vector<VertexData>& vertexData, unsigned int usageFlags = 0): Buffer(usageFlags) {
		m_VertexCount = vertexData.size();
		m_VertexData = vertexData.data();

		int updateRes = updateDeviceData();
		assert(updateRes == 0 && "updateDeviceData failed");
	}
*/
	VertexBuffer(VertexData* vertexData, int vertexCount, unsigned int usageFlags = 0): Buffer(usageFlags) {
		m_VertexCount = vertexCount;
		m_VertexData = vertexData;

		int updateRes = updateDeviceData();
		assert(updateRes == 0 && "updateDeviceData failed");
	}

	virtual ~VertexBuffer(void) {
		releaseBuffer();

		if(m_VertexData != NULL)
			delete[] m_VertexData;
		if(m_DeviceVertexData != NULL)
			delete[] m_DeviceVertexData;
		if(m_DeviceAdditionalData != NULL)
			delete[] m_DeviceAdditionalData;
	}

	VertexData& getVertex(int index) {
		return m_VertexData[index];
	}

	VertexData* getData() {
		return m_VertexData;
	}

	size_t getVertexCount() {
		return m_VertexCount;
	}

	size_t getDeviceSize() {
		size_t size = m_VertexCount*sizeof(DeviceVertexData);
		if(m_DeviceAdditionalData != NULL)
			size += m_VertexCount*sizeof(DeviceAdditionalUVData);
		return size;
	}

	// API dependant
	// 0 if success, !=0 error
	int sendToPipeline(unsigned int textureCoordinates);

	// API dependant
	// setup the pipeline for rendering using vertex buffers
	static void setupPipeline();
	static void releasePipeline();

	static size_t getBufferSizeFor(unsigned int vertexCount, unsigned int textureCoordinates = 1) {
		if(textureCoordinates == 1) {
			return vertexCount*sizeof(DeviceVertexData);
		}
		else {
			return vertexCount*( sizeof(DeviceVertexData)+sizeof(DeviceAdditionalUVData) );
		}
	}
};

